package org.aceor.mddal.gateway.router;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.aceor.mddal.common.sqlparser.result.SQLParsedResult;
import org.aceor.mddal.common.sqlparser.result.context.ConditionContext;
import org.aceor.mddal.common.sqlparser.result.context.RouteContext;
import org.aceor.mddal.common.sqlparser.result.context.ShardingContext;
import org.aceor.mddal.common.sqlparser.result.object.Column;
import org.aceor.mddal.common.sqlparser.result.object.Table;
import org.aceor.mddal.config.sharding.LogicTable;
import org.aceor.mddal.config.sharding.TableRule;
import org.aceor.mddal.gateway.executor.ExecuteContext;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import static org.aceor.mddal.common.utils.CommonUtil.print;

/**
 * Created by lxue on 16/4/11.
 */
//@Component
public class Router {

    private static final Logger log = LoggerFactory.getLogger(Router.class);

    private final LogicTable logicTable;

    private volatile static Router router;

    private Router(LogicTable logicTable) {
        this.logicTable = logicTable;
    }

    public static Router getInstance(LogicTable logicTable) {
        if (null == router) {
            synchronized (Router.class) {
                if (null == router) {
                    if (null == logicTable) {
                        throw new NullPointerException("第一次创建Router对象时logicTable不能为空!");
                    }
                    router = new Router(logicTable);
                }
            }
        }
        return router;
    }

    /**
     * sql解析时会使用到
     *
     * @param name
     * @param isTb 是否指分表字段
     * @return
     */
    public Collection<Column> getShardingColumns(String name, boolean isTb) {
        TableRule tableRule = logicTable.getTableRules().get(name);
        if (null == tableRule) {
            log.info("表 :{} 未做分库分库 ==> 没有找到分库分表字段", name);
            return Collections.emptyList();
        } else {
            Collection<Column> columns = Sets.newHashSet();
            if (isTb) {
                if (null != tableRule.getTbRule()
                        && !Strings.isNullOrEmpty(tableRule.getTbRule().getShardingColumn())) {
                    String tbShardingColumn = tableRule.getTbRule().getShardingColumn();
                    log.info("表 :{} 使用了分表规则,分表字段 :{}", name, tbShardingColumn);
                    columns.add(new Column(tbShardingColumn));
                }
            } else {
                if (null != tableRule.getDbRule()
                        && !Strings.isNullOrEmpty(tableRule.getDbRule().getShardingColumn())) {
                    String dbShardingColumn = tableRule.getDbRule().getShardingColumn();
                    log.info("表 :{} 使用了分库规则,分库字段 :{}", name, dbShardingColumn);
                    columns.add(new Column(dbShardingColumn));
                }
            }
            return columns;
        }
    }

    /**
     * 暂时支持分库分表字段单个
     *
     * @return map key: table名, value: 分库分表上下文
     */
    public Map<String, ShardingContext> getAllTableShardingColumnsMapping() {
        if (logicTable.getTableRules().isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, ShardingContext> mapping = Maps.newHashMap();
        for (Map.Entry<String, TableRule> entry : logicTable.getTableRules().entrySet()) {
            String tableName = entry.getKey();
            ShardingContext shardingContext = new ShardingContext();
            shardingContext.logicTableName = tableName;
            Collection<Column> shardingColumns = getShardingColumns(tableName, true);
            if (CollectionUtils.isNotEmpty(shardingColumns)) {
                shardingContext.tbShardingColumn = shardingColumns.iterator().next().getName();
                shardingContext.tbShardingFunction = entry.getValue().getTbRule();
                shardingContext.tbNameMessageFormat = entry.getValue().getTbNameMessageFormat();
            }
            shardingColumns = getShardingColumns(tableName, false);
            if (CollectionUtils.isNotEmpty(shardingColumns)) {
                shardingContext.dbShardingColumn = shardingColumns.iterator().next().getName();
                shardingContext.dbShardingFunction = entry.getValue().getDbRule();
                shardingContext.dbNameMessageFormat = entry.getValue().getDbNameMessageFormat();
            }
            mapping.put(tableName, shardingContext);
        }
        return mapping;
    }

    public ExecuteContext route(SQLParsedResult sqlParsedResult, String originalSql, List<Object> args) {
        ExecuteContext executeContext = new ExecuteContext();
        ConditionContext conditionContext = sqlParsedResult.conditionContext;
        ShardingContext shardingContext = sqlParsedResult.shardingContext;
        RouteContext routeContext = sqlParsedResult.routeContext;
        switch (shardingContext.getShardingType()) {
            /**
             * 1. 没有使用分库分表
             */
            case NO_SHARDING: {
                executeContext.sqlMap.put(getDefaultDbIndex(), Arrays.asList(new RealSqlContext(originalSql, shardingContext.logicTableName)));
                break;
            }

            /**
             * 2. 只分库不分表
             */
            case JUST_DB_SHARDING: {
                Column column = new Column(new Table(shardingContext.logicTableName), shardingContext.dbShardingColumn);
                print(Router.class, "db-column:" + column);
                Set<Object> values = RouteHelper.getAcceptableValues(
                        conditionContext.getConditionMap().get(column),
                        shardingContext.dbShardingFunction);
                RouteHelper.setExecuteSqlMapWhenSingleTbOrDbSharding(
                        executeContext,
                        column,
                        values,
                        shardingContext,
                        routeContext.getSqlBuilder(),
                        args,
                        null);
                break;
            }

            /**
             * 3. 只分表
             */
            case JUST_TB_SHARDING: {
                Column column = new Column(new Table(shardingContext.logicTableName), shardingContext.tbShardingColumn);
                print(Router.class, "tb-column:" + column);
                Set<Object> values =
                        RouteHelper.getAcceptableValues(
                                conditionContext.getConditionMap().get(column),
                                shardingContext.tbShardingFunction);

                if (CollectionUtils.isEmpty(values)) {
                    //TODO: 可以解决的 ==> 考虑算出来的还是字段不存在
                    throw new RuntimeException("咱不支持全表查询");
                }

                RouteHelper.setExecuteSqlMapWhenSingleTbOrDbSharding(
                        executeContext,
                        column,
                        values,
                        shardingContext,
                        routeContext.getSqlBuilder(),
                        args,
                        logicTable.getDefaultDbIndex());
                break;
            }

            /**
             * 4.又分库又分表
             */
            case TB_DB_BOTH_SHARDING: {
                /**
                 * 4.1 分库分表相同字段
                 */
                    if (Objects.equal(shardingContext.tbShardingColumn, shardingContext.dbShardingColumn)) {
                        Column column = new Column(new Table(shardingContext.logicTableName), shardingContext.tbShardingColumn);
                        print(Router.class, "db/tb-column:" + column);
                        Set<Object> values = RouteHelper.getAcceptableValues(
                                conditionContext.getConditionMap().get(column),
                                shardingContext.tbShardingFunction);

                        if (CollectionUtils.isEmpty(values)) {
                            //TODO: 可以解决的
                            throw new RuntimeException("咱不支持全表查询");
                        }

                        RouteHelper.setExecuteSqlMapWhenTbAndDbSameSharding(
                                executeContext,
                                column,
                                values,
                                shardingContext,
                                routeContext.getSqlBuilder(),
                                args);
                    }
                /**
                 * 4.2 分库分表不同字段
                 */
                    else {
                        Column tbColumn = new Column(new Table(shardingContext.logicTableName), shardingContext.tbShardingColumn);
                        print(Router.class, "tb-column:" + tbColumn);
                        Set<Object> tbValues = RouteHelper.getAcceptableValues(
                                conditionContext.getConditionMap().get(tbColumn),
                                shardingContext.tbShardingFunction);

                        Column dbColumn = new Column(new Table(shardingContext.logicTableName), shardingContext.dbShardingColumn);
                        print(Router.class, "db-column:" + dbColumn);
                        Set<Object> dBValues = RouteHelper.getAcceptableValues(
                                conditionContext.getConditionMap().get(dbColumn),
                                shardingContext.dbShardingFunction);
                        if (CollectionUtils.isEmpty(dBValues)
                                && CollectionUtils.isEmpty(tbValues)) {
                            //TODO: 可以解决的
                            throw new RuntimeException("咱不支持全表查询");
                        }
                        if (CollectionUtils.isEmpty(dBValues)) {
                            //只分表
                            RouteHelper.setExecuteSqlMapWhenSingleTbOrDbSharding(
                                    executeContext,
                                    tbColumn,
                                    tbValues,
                                    shardingContext,
                                    routeContext.getSqlBuilder(),
                                    args,
                                    logicTable.getDefaultDbIndex());
                        } else {
                            RouteHelper.setExecuteSqlMapWhenTbAndDbNotSameSharding(
                                    executeContext,
                                    tbColumn,
                                    dbColumn,
                                    tbValues,
                                    dBValues,
                                    shardingContext,
                                    routeContext.getSqlBuilder(),
                                    args);
                        }
                    }
                break;
            }

            default:
                throw new IllegalArgumentException("不支持的ShardingType :" + shardingContext.getShardingType());
        }
        return executeContext;
    }

    public LogicTable getLogicTable() {
        return logicTable;
    }

    private String getDefaultDbIndex() {
        String defaultDbIndex = logicTable.getDefaultDbIndex();
        return defaultDbIndex;
    }

    private static final class ShardingColumnHolder {
        public String dbShardingColumn;
        public String tbShardingColumn;
    }
}
